<?php
/**
 * @name PHP class for dynamically resizing, cropping, and rotating images for Scd_Util_Thumb purposes and either displaying them on-the-fly or saving them.
 *
 * LICENSE
 *
 * This source file is subject to the BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.njwz.cn/license/
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to Chaodong.Sheng <scd113@gmail.com> so we can send you a copy immediately.
 *
 * @category   Util of Scd Framework
 * @package    Scd
 * @copyright  Copyright (c) 2007 PHPXe Web Studio <http://www.phpxe.com>
 * @license    http://scd.phpxe.com/license/
 * @version    $Id: Thumb.php Sun Sep 02 16:59:33 CST 2007 16:59:33 Davy $
 */

class Scd_Util_Thumb {
	/**
     * Error message to display, if any
     *
     * @var string
     */
	var $errmsg;
	/**
     * Whether or not there is an error
     *
     * @var boolean
     */
	var $error;
	/**
     * Format of the image file
     *
     * @var string
     */
	var $format;
	/**
     * File name and path of the image file
     *
     * @var string
     */
	var $fileName;
	/**
     * Image meta data if any is available (jpeg/tiff) via the exif library
     *
     * @var array
     */
	var $imageMeta;
	/**
     * Current dimensions of working image
     *
     * @var array
     */
	var $currentDimensions;
	/**
     * New dimensions of working image
     *
     * @var array
     */
	var $newDimensions;
	/**
     * Image resource for newly manipulated image
     *
     * @var resource
     * @access private
     */
	var $newImage;
	/**
     * Image resource for image before previous manipulation
     *
     * @var resource
     * @access private
     */
	var $oldImage;
	/**
     * Image resource for image being currently manipulated
     *
     * @var resource
     * @access private
     */
	var $workingImage;
	/**
     * Percentage to resize image by
     *
     * @var int
     * @access private
     */
	var $percent;
	/**
     * Maximum width of image during resize
     *
     * @var int
     * @access private
     */
	var $maxWidth;
	/**
     * Maximum height of image during resize
     *
     * @var int
     * @access private
     */
	var $maxHeight;

	/**
     * Class constructor
     *
     * @param string $fileName
     * @return Scd_Util_Thumb
     */
	function Scd_Util_Thumb($fileName) {
		//make sure the GD library is installed
		if(!function_exists("gd_info")) {
			echo 'You do not have the GD Library installed.  This class requires the GD library to function properly.' . "\n";
			echo 'visit http://us2.php.net/manual/en/ref.image.php for more information';
			exit;
		}
		//initialize variables
		$this->errmsg               = '';
		$this->error                = false;
		$this->currentDimensions    = array();
		$this->newDimensions        = array();
		$this->fileName             = $fileName;
		$this->imageMeta			= array();
		$this->percent              = 100;
		$this->maxWidth             = 0;
		$this->maxHeight            = 0;

		//check to see if file exists
		if(!file_exists($this->fileName)) {
			$this->errmsg = 'File not found';
			$this->error = true;
		}
		//check to see if file is readable
		elseif(!is_readable($this->fileName)) {
			$this->errmsg = 'File is not readable';
			$this->error = true;
		}

		//if there are no errors, determine the file format
		if($this->error == false) {
			//check if gif
			if(stristr(strtolower($this->fileName),'.gif')) $this->format = 'GIF';
			//check if jpg
			elseif(stristr(strtolower($this->fileName),'.jpg') || stristr(strtolower($this->fileName),'.jpeg')) $this->format = 'JPG';
			//check if png
			elseif(stristr(strtolower($this->fileName),'.png')) $this->format = 'PNG';
			//unknown file format
			else {
				$this->errmsg = 'Unknown file format';
				$this->error = true;
			}
		}

		//initialize resources if no errors
		if($this->error == false) {
			switch($this->format) {
				case 'GIF':
					$this->oldImage = ImageCreateFromGif($this->fileName);
					break;
				case 'JPG':
					$this->oldImage = ImageCreateFromJpeg($this->fileName);
					break;
				case 'PNG':
					$this->oldImage = ImageCreateFromPng($this->fileName);
					break;
			}

			$size = GetImageSize($this->fileName);
			$this->currentDimensions = array('width'=>$size[0],'height'=>$size[1]);
			$this->newImage = $this->oldImage;
			$this->gatherImageMeta();
		}

		if($this->error == true) {
			$this->showErrorImage();
			break;
		}
	}

	/**
     * Must be called to free up allocated memory after all manipulations are done
     *
     */
	function destruct() {
		if(is_resource($this->newImage)) @ImageDestroy($this->newImage);
		if(is_resource($this->oldImage)) @ImageDestroy($this->oldImage);
		if(is_resource($this->workingImage)) @ImageDestroy($this->workingImage);
	}

	/**
     * Returns the current width of the image
     *
     * @return int
     */
	function getCurrentWidth() {
		return $this->currentDimensions['width'];
	}

	/**
     * Returns the current height of the image
     *
     * @return int
     */
	function getCurrentHeight() {
		return $this->currentDimensions['height'];
	}

	/**
     * Calculates new image width
     *
     * @param int $width
     * @param int $height
     * @return array
     */
	function calcWidth($width,$height) {
		$newWp = (100 * $this->maxWidth) / $width;
		$newHeight = ($height * $newWp) / 100;
		return array('newWidth'=>intval($this->maxWidth),'newHeight'=>intval($newHeight));
	}

	/**
     * Calculates new image height
     *
     * @param int $width
     * @param int $height
     * @return array
     */
	function calcHeight($width,$height) {
		$newHp = (100 * $this->maxHeight) / $height;
		$newWidth = ($width * $newHp) / 100;
		return array('newWidth'=>intval($newWidth),'newHeight'=>intval($this->maxHeight));
	}

	/**
     * Calculates new image size based on percentage
     *
     * @param int $width
     * @param int $height
     * @return array
     */
	function calcPercent($width,$height) {
		$newWidth = ($width * $this->percent) / 100;
		$newHeight = ($height * $this->percent) / 100;
		return array('newWidth'=>intval($newWidth),'newHeight'=>intval($newHeight));
	}

	/**
     * Calculates new image size based on width and height, while constraining to maxWidth and maxHeight
     *
     * @param int $width
     * @param int $height
     */
	function calcImageSize($width,$height) {
		$newSize = array('newWidth'=>$width,'newHeight'=>$height);

		if($this->maxWidth > 0) {

			$newSize = $this->calcWidth($width,$height);

			if($this->maxHeight > 0 && $newSize['newHeight'] > $this->maxHeight) {
				$newSize = $this->calcHeight($newSize['newWidth'],$newSize['newHeight']);
			}

			//$this->newDimensions = $newSize;
		}

		if($this->maxHeight > 0) {
			$newSize = $this->calcHeight($width,$height);

			if($this->maxWidth > 0 && $newSize['newWidth'] > $this->maxWidth) {
				$newSize = $this->calcWidth($newSize['newWidth'],$newSize['newHeight']);
			}

			//$this->newDimensions = $newSize;
		}

		$this->newDimensions = $newSize;
	}

	/**
     * Calculates new image size based percentage
     *
     * @param int $width
     * @param int $height
     */
	function calcImageSizePercent($width,$height) {
		if($this->percent > 0) {
			$this->newDimensions = $this->calcPercent($width,$height);
		}
	}

	/**
     * Displays error image
     *
     */
	function showErrorImage() {
		header('Content-type: image/png');
		$errImg = ImageCreate(220,25);
		$bgColor = imagecolorallocate($errImg,0,0,0);
		$fgColor1 = imagecolorallocate($errImg,255,255,255);
		$fgColor2 = imagecolorallocate($errImg,255,0,0);
		imagestring($errImg,3,6,6,'Error:',$fgColor2);
		imagestring($errImg,3,55,6,$this->errmsg,$fgColor1);
		imagepng($errImg);
		imagedestroy($errImg);
	}

	/**
     * Resizes image to maxWidth x maxHeight
     *
     * @param int $maxWidth
     * @param int $maxHeight
     */
	function resize($maxWidth = 0, $maxHeight = 0) {
		$this->maxWidth = $maxWidth;
		$this->maxHeight = $maxHeight;

		$this->calcImageSize($this->currentDimensions['width'],$this->currentDimensions['height']);

		if(function_exists("ImageCreateTrueColor")) {
			$this->workingImage = ImageCreateTrueColor($this->newDimensions['newWidth'],$this->newDimensions['newHeight']);
		}
		else {
			$this->workingImage = ImageCreate($this->newDimensions['newWidth'],$this->newDimensions['newHeight']);
		}

		ImageCopyResampled(
		$this->workingImage,
		$this->oldImage,
		0,
		0,
		0,
		0,
		$this->newDimensions['newWidth'],
		$this->newDimensions['newHeight'],
		$this->currentDimensions['width'],
		$this->currentDimensions['height']
		);

		$this->oldImage = $this->workingImage;
		$this->newImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->newDimensions['newWidth'];
		$this->currentDimensions['height'] = $this->newDimensions['newHeight'];
	}

	/**
	 * Resizes the image by $percent percent
	 *
	 * @param int $percent
	 */
	function resizePercent($percent = 0) {
		$this->percent = $percent;

		$this->calcImageSizePercent($this->currentDimensions['width'],$this->currentDimensions['height']);

		if(function_exists("ImageCreateTrueColor")) {
			$this->workingImage = ImageCreateTrueColor($this->newDimensions['newWidth'],$this->newDimensions['newHeight']);
		}
		else {
			$this->workingImage = ImageCreate($this->newDimensions['newWidth'],$this->newDimensions['newHeight']);
		}

		ImageCopyResampled(
		$this->workingImage,
		$this->oldImage,
		0,
		0,
		0,
		0,
		$this->newDimensions['newWidth'],
		$this->newDimensions['newHeight'],
		$this->currentDimensions['width'],
		$this->currentDimensions['height']
		);

		$this->oldImage = $this->workingImage;
		$this->newImage = $this->workingImage;
		$this->currentDimensions['width'] = $this->newDimensions['newWidth'];
		$this->currentDimensions['height'] = $this->newDimensions['newHeight'];
	}

	/**
	 * Crops the image from calculated center in a square of $cropSize pixels
	 *
	 * @param int $cropSize
	 */
	function cropFromCenter($cropSize) {
		if($cropSize > $this->currentDimensions['width']) $cropSize = $this->currentDimensions['width'];
		if($cropSize > $this->currentDimensions['height']) $cropSize = $this->currentDimensions['height'];

		$cropX = intval(($this->currentDimensions['width'] - $cropSize) / 2);
		$cropY = intval(($this->currentDimensions['height'] - $cropSize) / 2);

		if(function_exists("ImageCreateTrueColor")) {
			$this->workingImage = ImageCreateTrueColor($cropSize,$cropSize);
		}
		else {
			$this->workingImage = ImageCreate($cropSize,$cropSize);
		}

		imagecopyresampled(
		$this->workingImage,
		$this->oldImage,
		0,
		0,
		$cropX,
		$cropY,
		$cropSize,
		$cropSize,
		$cropSize,
		$cropSize
		);

		$this->oldImage = $this->workingImage;
		$this->newImage = $this->workingImage;
		$this->currentDimensions['width'] = $cropSize;
		$this->currentDimensions['height'] = $cropSize;
	}

	/**
	 * Advanced cropping function that crops an image using $startX and $startY as the upper-left hand corner.
	 *
	 * @param int $startX
	 * @param int $startY
	 * @param int $width
	 * @param int $height
	 */
	function crop($startX,$startY,$width,$height) {
		//make sure the cropped area is not greater than the size of the image
		if($width > $this->currentDimensions['width']) $width = $this->currentDimensions['width'];
		if($height > $this->currentDimensions['height']) $height = $this->currentDimensions['height'];
		//make sure not starting outside the image
		if(($startX + $width) > $this->currentDimensions['width']) $startX = ($this->currentDimensions['width'] - $width);
		if(($startY + $height) > $this->currentDimensions['height']) $startY = ($this->currentDimensions['height'] - $height);
		if($startX < 0) $startX = 0;
		if($startY < 0) $startY = 0;

		if(function_exists("ImageCreateTrueColor")) {
			$this->workingImage = ImageCreateTrueColor($width,$height);
		}
		else {
			$this->workingImage = ImageCreate($width,$height);
		}

		imagecopyresampled(
		$this->workingImage,
		$this->oldImage,
		0,
		0,
		$startX,
		$startY,
		$width,
		$height,
		$width,
		$height
		);

		$this->oldImage = $this->workingImage;
		$this->newImage = $this->workingImage;
		$this->currentDimensions['width'] = $width;
		$this->currentDimensions['height'] = $height;
	}

	/**
	 * Outputs the image to the screen, or saves to $name if supplied.  Quality of JPEG images can be controlled with the $quality variable
	 *
	 * @param int $quality
	 * @param string $name
	 */
	function show($quality=100,$name = '') {
		switch($this->format) {
			case 'GIF':
				if($name != '') {
					ImageGif($this->newImage,$name);
				}
				else {
					header('Content-type: image/gif');
					ImageGif($this->newImage);
				}
				break;
			case 'JPG':
				if($name != '') {
					ImageJpeg($this->newImage,$name,$quality);
				}
				else {
					header('Content-type: image/jpeg');
					ImageJpeg($this->newImage,'',$quality);
				}
				break;
			case 'PNG':
				if($name != '') {
					ImagePng($this->newImage,$name);
				}
				else {
					header('Content-type: image/png');
					ImagePng($this->newImage);
				}
				break;
		}
	}

	/**
	 * Saves image as $name (can include file path), with quality of # percent if file is a jpeg
	 *
	 * @param string $name
	 * @param int $quality
	 */
	function save($name,$quality=100) {
		$this->show($quality,$name);
	}

	/**
	 * Creates Apple-style reflection under image, optionally adding a border to main image
	 *
	 * @param int $percent
	 * @param int $reflection
	 * @param int $white
	 * @param bool $border
	 * @param string $borderColor
	 */
	function createReflection($percent,$reflection,$white,$border = true,$borderColor = '#a4a4a4') {
		$width = $this->currentDimensions['width'];
		$height = $this->currentDimensions['height'];

		$reflectionHeight = intval($height * ($reflection / 100));
		$newHeight = $height + $reflectionHeight;
		$reflectedPart = $height * ($percent / 100);

		$this->workingImage = ImageCreateTrueColor($width,$newHeight);

		ImageAlphaBlending($this->workingImage,true);

		$colorToPaint = ImageColorAllocateAlpha($this->workingImage,255,255,255,0);
		ImageFilledRectangle($this->workingImage,0,0,$width,$newHeight,$colorToPaint);

		imagecopyresampled(
		$this->workingImage,
		$this->newImage,
		0,
		0,
		0,
		$reflectedPart,
		$width,
		$reflectionHeight,
		$width,
		($height - $reflectedPart));
		$this->imageFlipVertical();

		imagecopy($this->workingImage,$this->newImage,0,0,0,0,$width,$height);

		imagealphablending($this->workingImage,true);

		for($i=0;$i<$reflectionHeight;$i++) {
			$colorToPaint = imagecolorallocatealpha($this->workingImage,255,255,255,($i/$reflectionHeight*-1+1)*$white);
			imagefilledrectangle($this->workingImage,0,$height+$i,$width,$height+$i,$colorToPaint);
		}

		if($border == true) {
			$rgb = $this->hex2rgb($borderColor,false);
			$colorToPaint = imagecolorallocate($this->workingImage,$rgb[0],$rgb[1],$rgb[2]);
			imageline($this->workingImage,0,0,$width,0,$colorToPaint); //top line
			imageline($this->workingImage,0,$height,$width,$height,$colorToPaint); //bottom line
			imageline($this->workingImage,0,0,0,$height,$colorToPaint); //left line
			imageline($this->workingImage,$width-1,0,$width-1,$height,$colorToPaint); //right line
		}

		$this->oldImage = $this->workingImage;
		$this->newImage = $this->workingImage;
		$this->currentDimensions['width'] = $width;
		$this->currentDimensions['height'] = $newHeight;
	}

	/**
	 * Inverts working image, used by reflection function
	 * 
	 * @access	private
	 */
	function imageFlipVertical() {
		$x_i = imagesx($this->workingImage);
		$y_i = imagesy($this->workingImage);

		for($x = 0; $x < $x_i; $x++) {
			for($y = 0; $y < $y_i; $y++) {
				imagecopy($this->workingImage,$this->workingImage,$x,$y_i - $y - 1, $x, $y, 1, 1);
			}
		}
	}

	/**
	 * Converts hexidecimal color value to rgb values and returns as array/string
	 *
	 * @param string $hex
	 * @param bool $asString
	 * @return array|string
	 */
	function hex2rgb($hex, $asString = false) {
		// strip off any leading #
		if (0 === strpos($hex, '#')) {
			$hex = substr($hex, 1);
		} else if (0 === strpos($hex, '&H')) {
			$hex = substr($hex, 2);
		}

		// break into hex 3-tuple
		$cutpoint = ceil(strlen($hex) / 2)-1;
		$rgb = explode(':', wordwrap($hex, $cutpoint, ':', $cutpoint), 3);

		// convert each tuple to decimal
		$rgb[0] = (isset($rgb[0]) ? hexdec($rgb[0]) : 0);
		$rgb[1] = (isset($rgb[1]) ? hexdec($rgb[1]) : 0);
		$rgb[2] = (isset($rgb[2]) ? hexdec($rgb[2]) : 0);

		return ($asString ? "{$rgb[0]} {$rgb[1]} {$rgb[2]}" : $rgb);
	}

	/**
     * Reads selected exif meta data from jpg images and populates $this->imageMeta with appropriate values if found
     *
     */
	function gatherImageMeta() {
		//only attempt to retrieve info if exif exists
		if(function_exists("exif_read_data") && $this->format == 'JPG') {
			$imageData = exif_read_data($this->fileName);
			if(isset($imageData['Make']))
			$this->imageMeta['make'] = ucwords(strtolower($imageData['Make']));
			if(isset($imageData['Model']))
			$this->imageMeta['model'] = $imageData['Model'];
			if(isset($imageData['COMPUTED']['ApertureFNumber'])) {
				$this->imageMeta['aperture'] = $imageData['COMPUTED']['ApertureFNumber'];
				$this->imageMeta['aperture'] = str_replace('/','',$this->imageMeta['aperture']);
			}
			if(isset($imageData['ExposureTime'])) {
				$exposure = explode('/',$imageData['ExposureTime']);
				$exposure = round($exposure[1]/$exposure[0],-1);
				$this->imageMeta['exposure'] = '1/' . $exposure . ' second';
			}
			if(isset($imageData['Flash'])) {
				if($imageData['Flash'] > 0) {
					$this->imageMeta['flash'] = 'Yes';
				}
				else {
					$this->imageMeta['flash'] = 'No';
				}
			}
			if(isset($imageData['FocalLength'])) {
				$focus = explode('/',$imageData['FocalLength']);
				$this->imageMeta['focalLength'] = round($focus[0]/$focus[1],2) . ' mm';
			}
			if(isset($imageData['DateTime'])) {
				$date = $imageData['DateTime'];
				$date = explode(' ',$date);
				$date = str_replace(':','-',$date[0]) . ' ' . $date[1];
				$this->imageMeta['dateTaken'] = date('m/d/Y g:i A',strtotime($date));
			}
		}
	}

	/**
     * Rotates image either 90 degrees clockwise or counter-clockwise
     *
     * @param string $direction
     */
	function rotateImage($direction = 'CW') {
		if($direction == 'CW') {
			$this->workingImage = imagerotate($this->workingImage,-90,0);
		}
		else {
			$this->workingImage = imagerotate($this->workingImage,90,0);
		}
		$newWidth = $this->currentDimensions['height'];
		$newHeight = $this->currentDimensions['width'];
		$this->oldImage = $this->workingImage;
		$this->newImage = $this->workingImage;
		$this->currentDimensions['width'] = $newWidth;
		$this->currentDimensions['height'] = $newHeight;
	}
}
?>